/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ts, NodePredicate, rule } from 'qvog-dsl';

const SEVERITY = 1;
const DOC_PATH = 'docs/init-declarations.md';
const DESCRIPTION = 'Require or disallow initialization in variable declarations';

interface RuleOption {
  mode?: 'always' | 'never';
  ignoreForLoopInit?: boolean;
}

const VariableNode: NodePredicate<ts.VariableDeclaration> = {
  is(node: ts.Node): node is ts.VariableDeclaration {
    return ts.isVariableDeclaration(node);
  }
};

export default rule<{ ruleOption: RuleOption }>()
  .match(VariableNode)
  .when((node, { props }) => {
    const options = props.ruleOption || {};
    const mode = options.mode ?? 'always';
    const ignoreForLoopInit = options.ignoreForLoopInit ?? false;

    // 跳过 declare 模块、全局命名空间等
    if (isDeclaredNamespace(node)) { return false };

    // 如果设置了忽略 for 初始化并且当前节点在 for 中，跳过
    if (ignoreForLoopInit && isInForLoop(node)) { return false };

    const hasInitializer = !!node.initializer;
    const isConst = ts.isVariableDeclarationList(node.parent) &&
      (node.parent.flags & ts.NodeFlags.Const) !== 0;

    if (mode === 'always') {
      return !hasInitializer;
    } else {
      return hasInitializer && !isConst;
    }
  })
  .report({
    severity: SEVERITY,
    description: DESCRIPTION,
    docPath: DOC_PATH
  });

function isInForLoop(node: ts.VariableDeclaration): boolean {
  const parent = node.parent?.parent;
  return parent && (
    ts.isForStatement(parent) ||
    ts.isForInStatement(parent) ||
    ts.isForOfStatement(parent)
  );
}

function isDeclaredNamespace(node: ts.Node): boolean {
  let current: ts.Node | undefined = node;
  while (current) {
    if (
      ts.isModuleDeclaration(current) &&
      current.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword)
    ) {
      return true;
    }
    if (
      ts.isVariableStatement(current) &&
      current.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword)
    ) {
      return true;
    }
    current = current.parent;
  }
  return false;
}
